﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Specialized;
using System.ComponentModel;
using FinPlusCommon;
using log = Logger.Logger;
using u = FinPlusUtility.Utility;

namespace FinPlusComponents
{
    public class QLEngineDefaultCurves : FinPlusComponent
    {
        public override string Id { get; set; }
        public override string[] States { get { return new string[] { }; } }
        public override string[] TestStates { get { return new string[] { "IsValid" }; } }
        public override IFinPlusFunctions Model { get { return null; } }
        public override string ModelTest { get { return null; } }
        public override bool IsValid { get; set; }

        private enum Params { CurveName, LinkedCurve };
        private enum BuildEvents { Update, Refresh, UpdateAndRefresh }
        private enum DockControls { FinPlusDataConn, FinPlusChildService, QLAnalytics, Default }
        private enum Connection { Instruments, DefaultCurveBuilds, InstrumentBuilds, Exception }
        
        private IFinPlusComponent _instrumentConn, _defaultCurveBuildConn, _instrumentBuildConn;
        private QLAnalytics _analytics;
        private readonly DefaultCurves _defaultCurves;
        private readonly BuildEvents _buildEvents;
        private bool _hasInitialised;

        //construct
        public QLEngineDefaultCurves(string buildEvents) 
        {
            _buildEvents = u.EnumTryParse<BuildEvents>(buildEvents, BuildEvents.UpdateAndRefresh);
            _defaultCurves = new DefaultCurves();
        }

        //common control interface
        public override void Dock(object component, string dock)
        {
            try
            {
                switch (u.EnumTryParse<DockControls>(component.GetType().Name, DockControls.Default))
                {
                    case DockControls.QLAnalytics: AddQLAnalytics((QLAnalytics)component, dock); break;
                    case DockControls.FinPlusChildService:
                    case DockControls.FinPlusDataConn:
                    case DockControls.Default: AddConnection((IFinPlusComponent)component, dock); break;
                }
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        public override void Initialise()
        {
            try
            {
                if (_hasInitialised) return;

                foreach (var defaultCurveBuild in _defaultCurveBuildConn.Model.Values)
                {
                    var curveName = defaultCurveBuild[Params.CurveName.ToString()].ToString();
                    var instruments = _instrumentConn.Model.ToList(Params.LinkedCurve.ToString(), curveName, ',');
                    _defaultCurves.TryAdd(curveName, new DefaultCurve(_analytics, curveName, defaultCurveBuild, _instrumentBuildConn.Model, instruments, _buildEvents));
                }

                IsValid = _hasInitialised = true;
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        public override void Dispose() 
        {
            try
            {
                if (_instrumentConn != null)
                    _instrumentConn.Dispose();
                if (_defaultCurveBuildConn != null)
                    _defaultCurveBuildConn.Dispose();
                if (_analytics != null)
                    _analytics.Dispose();
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Warning, e.Message, e);
            }
        
        }

        public override void CommandRouter(IFinPlusFunction func) { }//levels via data conn trap/inject through conn observability

        //private
        private void AddQLAnalytics(QLAnalytics analytics, string dock)
        {
            try
            {
                _analytics = analytics;
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void AddConnection(IFinPlusComponent conn, string dock)
        {
            try
            {
                switch (u.EnumTryParse<Connection>(dock, Connection.Exception))
                {
                    case Connection.DefaultCurveBuilds: _defaultCurveBuildConn = conn; break;
                    case Connection.Instruments: _instrumentConn = conn; break;
                    case Connection.InstrumentBuilds: _instrumentBuildConn = conn; break;
                    case Connection.Exception: throw new NotImplementedException();
                }
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        //all helper class of component FinPlusEngineDefaultCurves 
        private class DefaultCurves : ConcurrentDictionary<string, DefaultCurve> { }

        private class DefaultCurve : ConcurrentDictionary<string, CdsSpread>
        {
            private enum Params { LevelUpdate, Refresh, Shock, Instrument, CurveName, NumSpread, RecoveryRate, DayCount, Holidays };

            private IList<IFinPlusFunction> _instruments;
            private QLAnalytics _analytics;
            private IFinPlusFunctions _instrumentBuilds;
            private IFinPlusFunction _defaultCurveBuild;
            private BuildEvents _buildEvents;
            private string _curveName, _dayCount, _holidays;
            private int _numSpread, _spreadCount;
            private double _recoveryRate;
  
            //construct
            public DefaultCurve(QLAnalytics analytics, string curveName, IFinPlusFunction defaultCurveBuild, IFinPlusFunctions instrumentBuilds, IList<IFinPlusFunction> instruments, BuildEvents buildEvents)
            {
                _analytics = analytics;
                _curveName = curveName;
                _defaultCurveBuild = defaultCurveBuild;
                _instrumentBuilds = instrumentBuilds;
                _instruments = instruments;
                _recoveryRate = (double)_defaultCurveBuild.Value(Params.RecoveryRate.ToString());
                _dayCount = _defaultCurveBuild[Params.DayCount.ToString()].ToString();
                _holidays = _defaultCurveBuild[Params.Holidays.ToString()].ToString();
                _numSpread = (int)_defaultCurveBuild.Value(Params.NumSpread.ToString());
                _buildEvents = buildEvents;
                Initialise();
            }

            //public
            public void Initialise()
            {
                foreach (var instrument in _instruments)
                {
                    if (_buildEvents == BuildEvents.Refresh || _buildEvents == BuildEvents.UpdateAndRefresh)
                        instrument.Get(Params.Refresh.ToString(), string.Empty, Curve_Refresh);

                    instrument.Get(Params.LevelUpdate.ToString(), new Function(), Curve_Update);
                    instrument.Get(Params.Shock.ToString(), new Function(), Curve_Shock);

                    var instrumentName = instrument[Params.Instrument.ToString()].ToString();
                    var instrumentBuild = _instrumentBuilds.First("Name='Instrument' And Value='" + instrumentName + "'");

                    if (instrumentBuild != null)
                    {
                        this[instrumentName] = AddSpread(instrumentName, instrument, instrumentBuild);
                        instrument.Get(Params.Refresh.ToString(), string.Empty).Set(instrumentName);
                    }
                }
            }

            //private
            private CdsSpread AddSpread(string instrumentName, IFinPlusFunction instrument, IFinPlusFunction instrumentBuild)
            {
                _spreadCount++;
                return new CdsSpread(_analytics, instrumentName, instrument, instrumentBuild, _recoveryRate);
            }

            private void BuildCurve()
            {
                try
                {
                    string res;
                    lock (_analytics.QL)
                        res = _analytics.QL.DefaultCurve(_analytics.MarketName, _curveName, _analytics.DiscountCurve, _analytics.AsOf, Spreads(_analytics.BuySell), _recoveryRate, _dayCount, _holidays);
                }
                catch (Exception e)
                {
                    log.Log(Logger.Level.Error, e.Message, e);
                }
            
            }

            private bool IsValid()
            {
                if (_numSpread != _spreadCount)
                    return false;

                foreach (var spread in this.Values)
                    if (Double.IsNaN(spread.Value))
                        return false;

                return true;
            }

            private string Spreads(string buySell)
            {
                return string.Join(",", this.Values.Select(f => f.Build(buySell)).ToArray<string>());
            }
            
            //events
            private void Curve_Update(object sender, PropertyChangedEventArgs a)
            {
                var level = (IFinPlusFunction)((IFinPlusItem)sender).Object;
                var instrument = level[Params.Instrument.ToString()].ToString();

                if (this[instrument].Update(level) && IsValid()) 
                    BuildCurve();
            }

            private void Curve_Refresh(object sender, PropertyChangedEventArgs a)
            {
                if (IsValid() && (((IFinPlusItem)sender).Value.Equals(_curveName)))
                    BuildCurve();
            }

            private void Curve_Shock(object sender, PropertyChangedEventArgs a)
            {
                var shock = (IFinPlusFunction)((IFinPlusItem)sender).Object;
                var curveName = shock[Params.CurveName.ToString()].ToString();
                if (curveName.Equals(_curveName))
                {
                    var instrumentName = shock[Params.Instrument.ToString()].ToString();
                    var value = (int)shock.Get(Params.Shock.ToString()).Object;
                    this[instrumentName].Shock = value;
                    if (value != 0 && IsValid()) BuildCurve();
                }
            }
        }

        private class CdsSpread 
        {
            public double Value { get { return _level.Value; } }
            public int Shock { get; set; }
            public string Name { get { return _instrumentName; } }

            private enum Params { ShockUnit, Maturity, SettlementDays, DayCount, PremFreq, PremConv, Holidays };
            private QLAnalytics _analytics;
            private FinPlusLevel _level;
            private IFinPlusFunction _instrumentBuild, _instrument;
            private string _instrumentName, _maturity, _dayCount, _premConv, _holidays, _premFreq;
            private int _settlementDays;
            private double _recoveryRate, _shockUnit;

            public CdsSpread(QLAnalytics analytics, string instrumentName, IFinPlusFunction instrument, IFinPlusFunction instrumentBuild, double recoveryRate)
            {
                _analytics = analytics;
                _instrumentName = instrumentName;
                _instrument = instrument;
                _instrumentBuild = instrumentBuild;
                _recoveryRate = recoveryRate;

                _settlementDays = (int)_instrumentBuild.Value(Params.SettlementDays.ToString());
                _level = new FinPlusLevel(analytics.BuySell);
                _maturity = _instrumentBuild[Params.Maturity.ToString()].ToString();
                _settlementDays = (int)_instrumentBuild.Value(Params.SettlementDays.ToString());
                _dayCount = _instrumentBuild[Params.DayCount.ToString()].ToString();
                _premFreq = _instrumentBuild[Params.PremFreq.ToString()].ToString();
                _premConv = _instrumentBuild[Params.PremConv.ToString()].ToString();
                _holidays = _instrumentBuild[Params.Holidays.ToString()].ToString();
                _shockUnit = (double)_instrument.Get(Params.ShockUnit.ToString()).Object;
            }

            public string Build(string buySell)
            {
                lock (_analytics.QL)
                    return _analytics.QL.CdsSpread(_analytics.MarketName, _instrumentName, _level.Value + _shockUnit * Shock, _recoveryRate, _maturity, _settlementDays, _analytics.DiscountCurve, _premFreq, _dayCount, _premConv, _holidays);
            }

            internal bool Update(IFinPlusFunction level)
            {
                lock (_analytics.QL)
                   return _level.LevelUpdate(level);
            }
        }
    }
}
